

#include <SPI.h>
#include <RF24.h>
#include <Wire.h>
#include <EEPROM.h>
#include <Adafruit_NeoPixel.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

#define OLED_REST 4
Adafruit_SSD1306 display(128,64,&Wire,OLED_REST);

#define LED_PIN 10
#define NUMPIXELS 4
Adafruit_NeoPixel led_strip(NUMPIXELS, LED_PIN, NEO_RGB + NEO_KHZ800);

// Define the digital inputs
#define t1 7 // Toggle switch 1

#define b1 4   // Button 1
#define b2 2   // Button 2
#define b3 8   // Button 3
#define b4 9   // Button 4

#define IDX_J1FlipX 0
#define IDX_J1FlipY 1
#define IDX_J2FlipX 2
#define IDX_J2FlipY 3
#define IDX_COLOR

char *ConfigName[]= {"J1FlipX","J1FlipY","J2FlipX","J2FlipY","Color"};
byte ConfigValueMin[] = {0,0,0,0,0};
byte ConfigValueMax[] = {1,1,1,1,10};
byte ConfigValue[] = {0,0,0,0,0};

int MenuSelectIndex = 0;
bool ConfigMode = false;

int color_table[10][3]={
  {0,0,0},  
  {255,0,0},
  {255,130,0},
  {84,255,0},
  {0,253,214},
  {0,0,255},
  {222,0,255},
  {255,0,156},  
  {255,255,0},  
  {0,255,0}    
};

float light_h = 0,light_s = 0,light_v = 0;

bool bShowGraphics = true;
bool bLedOn = false;

bool bButton1Down = false;
bool bButton2Down = false;
bool bButton3Down = false;
bool bButton4Down = false;

int  iCycle = 0;

RF24 radio(5, 6);   //radio(CE, CSN)
const byte address[6] = "10001"; // Address
 
// Max size of this struct is 32 bytes - NRF24L01 buffer limit
struct Data_Package {
  byte j1PotX;
  byte j1PotY;
  byte j2PotX;
  byte j2PotY;  
  byte pot1;
  byte pot2;
  byte switch1;
  byte switch2;
  byte switch3;
  byte switch4;
  byte button1;
  byte button2;
  byte button3;
  byte button4;
};

Data_Package data,last_data; //Create a variable with the above structure

int j1PotXCenter = 50;
int j1PotXRange = 50;

int j1PotYCenter = 50;
int j1PotYRange = 50;

int j2PotXCenter = 50;
int j2PotXRange = 50;

int j2PotYCenter = 50;
int j2PotYRange = 50;

void setup() 
{
  //Serial.begin(9600);

  InitEEPROM();
  
  
  led_strip.begin();

  display.begin(SSD1306_SWITCHCAPVCC,0x3c);  
  display.setTextColor(SSD1306_BLACK);
 
  // Define the radio communication
  radio.begin();
  radio.setChannel(115);
  radio.openWritingPipe(address);
  radio.setAutoAck(false);
  radio.setDataRate(RF24_250KBPS);
  radio.setPALevel(RF24_PA_LOW);
  radio.stopListening();
  
  // Activate the Arduino internal pull-up resistors
  pinMode(t1, INPUT_PULLUP);
  pinMode(b1, INPUT_PULLUP);
  pinMode(b2, INPUT_PULLUP);
  pinMode(b3, INPUT_PULLUP);
  pinMode(b4, INPUT_PULLUP);

  AutoCalibrate();

  ResetData();
  DrawLogo(); 
}



void loop() 
{ 
  iCycle = (iCycle+5) % 10;  
  
  int j1PotX = max(min(analogRead(A1),j1PotXCenter+j1PotXRange),j1PotXCenter-j1PotXRange);     
  data.j1PotX = map(j1PotX,j1PotXCenter-j1PotXRange, j1PotXCenter+j1PotXRange, 0, 100); 
  if (ConfigValue[IDX_J1FlipX] == 1)
  {
    data.j1PotX = 100 - data.j1PotX;
  }

  int j1PotY = max(min(analogRead(A0),j1PotYCenter+j1PotYRange),j1PotYCenter-j1PotYRange);    
  data.j1PotY = map(j1PotY,j1PotYCenter-j1PotYRange, j1PotYCenter+j1PotYRange, 0, 100); 
  if (ConfigValue[IDX_J1FlipY] == 1)
  {
    data.j1PotY = 100 - data.j1PotY;
  }
  
  data.j2PotX = data.j1PotX;
  data.j2PotY = data.j1PotY;

  data.pot1 = map(analogRead(A2), 0, 1023, 0, 100);
  data.pot2 = map(analogRead(A3), 0, 1023, 0, 100);

  // Read all digital inputs
  data.switch1 = digitalRead(t1);
    
  data.button1 = 1 - digitalRead(b1);
  data.button2 = 1 - digitalRead(b2);
  data.button3 = 1 - digitalRead(b3);
  data.button4 = 1 - digitalRead(b4);

  if (ConfigMode)
  {
    HandleMenuInput();
    ShowMenu();    
  }
  else
  {
    HandleControlInput();
    radio.write(&data, sizeof(Data_Package));  
    ShowStatus();    
  }
  if (last_data.pot1 != data.pot1 || last_data.pot2 != data.pot2 || last_data.j1PotX != data.j1PotX)
  {
    UpdateLedStrip();
  }
  CopyData();
}

void HSV2RGB(float h,float s,float v,float &r,float &g,float &b)
{
  // R,G,B from 0-255, H from 0-360, S,V from 0-100
  
  float RGB_min, RGB_max;
  RGB_max = v*2.55f;
  RGB_min = RGB_max*(100 - s) / 100.0f;

  int i = h / 60;
  int difs = (int)h % 60; // factorial part of h

  // RGB adjustment amount by hue 
  float RGB_Adj = (RGB_max - RGB_min)*difs / 60.0f;

  switch (i) {
  case 0:
    r = RGB_max;
    g = (RGB_min + RGB_Adj);
    b = RGB_min;
    break;
  case 1:
    r = (RGB_max - RGB_Adj);
    g = RGB_max;
    b = RGB_min;
    break;
  case 2:
    r = RGB_min;
    g = RGB_max;
    b = (RGB_min + RGB_Adj);
    break;
  case 3:
    r = RGB_min;
    g = (RGB_max - RGB_Adj);
    b = RGB_max;
    break;
  case 4:
    r = (RGB_min + RGB_Adj);
    g = RGB_min;
    b = RGB_max;
    break;
  default:    // case 5:
    r = RGB_max ;
    g = RGB_min ;
    b = (RGB_max - RGB_Adj);
    break;
  }
}

void HandleControlInput()
{
   if (data.button1 == 1)
  {
    if (!bButton1Down)
    {
      bButton1Down = true;
      //do some btn1 staff    
    }      
  }
  else 
  {
    bButton1Down = false;
  }

  if (data.button2 == 1)
  {
    if (!bButton2Down)
    {
      bButton2Down = true;
      //do some btn2 staff      
      bShowGraphics = !bShowGraphics;    
    }      
  }
  else 
  {
    bButton2Down = false;
  }

  if (data.button3 == 1)
  {
    if (!bButton3Down)
    {
      bButton3Down = true;
      //do some btn3 staff  
      bLedOn = !bLedOn;
      UpdateLedStrip();    
    }      
  }
  else 
  {
    bButton3Down = false;
  }

  if (data.button4 == 1)
  {
    if (!bButton4Down)
    {
      bButton4Down = true;
      //do some btn3 staff
      ConfigMode = true;
    }      
  }
  else 
  {
    bButton4Down = false;
  }    
}

void HandleMenuInput()
{
   if (data.button1 == 1)
  {
    if (!bButton1Down)
    {
      bButton1Down = true;
      //do some btn1 staff
      //
     ConfigValue[MenuSelectIndex] = ConfigValue[MenuSelectIndex] + 1;
     if (ConfigValue[MenuSelectIndex] > ConfigValueMax[MenuSelectIndex])
     {
      ConfigValue[MenuSelectIndex] = ConfigValueMin[MenuSelectIndex];
     }
    }      
  }
  else 
  {
    bButton1Down = false;
  }

  if (data.button2 == 1)
  {
    if (!bButton2Down)
    {
      bButton2Down = true;
      //do some btn2 staff       
      MenuSelectIndex--;
      if (MenuSelectIndex < 0)
      {
        MenuSelectIndex = sizeof(ConfigValue) / sizeof(ConfigValue[0]) - 1;
      }      
    }      
  }
  else 
  {
    bButton2Down = false;
  }

  if (data.button3 == 1)
  {
    if (!bButton3Down)
    {
      bButton3Down = true;
      //do some btn3 staff   
      MenuSelectIndex++;
      MenuSelectIndex %= sizeof(ConfigValue) / sizeof(ConfigValue[0]);   
    }      
  }
  else 
  {
    bButton3Down = false;
  }

  if (data.button4 == 1)
  {
    if (!bButton4Down)
    {
      bButton4Down = true;
      //do some btn3 staff     
      UpdateEEPROM();
      ConfigMode= false;
    }      
  }
  else 
  {
    bButton4Down = false;
  }
  
}

char buff[128];

void ShowStatus()
{      
  display.fillRect(0,0,display.width(),display.height(),SSD1306_WHITE);
   
  if (bShowGraphics)
  {    
     display.fillCircle(64,32,26 + (iCycle / 10.0f)* 3,SSD1306_BLACK);      
     display.drawCircle(64,32,30,SSD1306_BLACK);      
     if (data.button4 == 1)
     {
       display.drawRect(64 + (1.0f - data.j1PotX/100.0f) * 28 - 14-5,32+(1.0f - data.j1PotY/100.0f) * 28 - 14 -5,10,10,SSD1306_WHITE);
     }
     else
     {
       display.drawRect(64 + (1.0f - data.j1PotX/100.0f) * 28 - 14-5,32+(1.0f - data.j1PotY/100.0f) * 28 - 14 -5,10,10,SSD1306_WHITE);
     }
      
      if (data.switch1==1)
      {
        display.drawRect(24,6,8,8,SSD1306_BLACK);
      }
      else
      {
        display.fillRect(24,6,8,8,SSD1306_BLACK);
      }

      display.drawRect(2,2,8,24,SSD1306_BLACK);        
      display.fillRect(2,2 + 24 * (1.0f-data.pot2 / 100.0f),8,24 * (data.pot2 / 100.0f)+1,SSD1306_BLACK);

      display.drawRect(2,32,8,24,SSD1306_BLACK);        
      display.fillRect(2,32 + 24 * (1.0f-data.pot1 / 100.0f),8,24 * (data.pot1 / 100.0f)+1,SSD1306_BLACK);
   
       if (data.button1 == 1)
       {
          display.fillRect(110,2,10,8,SSD1306_BLACK);
       }
       else
       {
          display.drawRect(110,2,10,8,SSD1306_BLACK);
       }

       if (data.button2 == 1)
       {
          display.fillRect(118,26,10,8,SSD1306_BLACK);
       }
       else
       {
          display.drawRect(118,26,10,8,SSD1306_BLACK);
       }

       if (data.button3 == 1)
       {
          display.fillRect(118,42,10,8,SSD1306_BLACK);
       }
       else
       {
          display.drawRect(118,42,10,8,SSD1306_BLACK);
       }           
  }
  else
  {
     display.setTextColor(SSD1306_INVERSE); 
     display.setTextSize(1); 
        display.setCursor(0,4);      
        sprintf(buff," [J] %3.3d %3.3d %3.3d %3.3d\n\n [P] %3.3d %3.3d %3.3d %3.3d\n\n [S] %3.3d %3.3d %3.3d %3.3d\n\n [B] %3.3d %3.3d %3.3d %3.3d\n",
         data.j1PotX,data.j1PotY,
         data.j2PotX,data.j2PotY,
         data.pot1,0,data.pot2,0,
         data.switch1,data.switch2,data.switch3,data.switch4,         
         data.button1,data.button2,data.button3,data.button4
       );
        display.print(buff); 
  }  

  display.display();
  delay(5);  
}

void ShowMenu()
{      
  display.fillRect(0,0,display.width(),display.height(),SSD1306_BLACK);
  display.setTextSize(1); 
  display.setTextColor(SSD1306_WHITE);
  
  for(int i = 0; i < sizeof(ConfigName) / sizeof(ConfigName[0]);i++)
  {
   display.setCursor(0,1+i*12);      
   sprintf(buff,"%-8.8s         %2.2d\n",ConfigName[i],ConfigValue[i]);         
   display.print(buff); 
   
   if (MenuSelectIndex == i)
    {      
      display.fillRect(80,i*12,128-80,9,SSD1306_INVERSE);
    }
  }  
  display.display();
  delay(5);  
}



void AutoCalibrate()
{
  j1PotXCenter = analogRead(A1);
  j1PotXRange = min((1023 - j1PotXCenter),j1PotXCenter);
  
  j1PotYCenter = analogRead(A0);
  j1PotYRange = min((1023 - j1PotYCenter),j1PotYCenter);
    
  j2PotXCenter = analogRead(A2);
  j2PotXRange = min((1023 - j2PotXCenter),j2PotXCenter);
  
  j2PotYCenter = analogRead(A3);  
  j2PotYRange = min((1023 - j2PotYCenter),j2PotYCenter);
}

void SetLedColor(int index,int r, int g, int b)
{
  led_strip.setBrightness(25);
  led_strip.setPixelColor(index, r, g, b);  
  led_strip.show(); 
}

void SetLedColors(int r, int g, int b)
{
  led_strip.setBrightness(25);
  led_strip.setPixelColor(0, r, g, b);  
  led_strip.setPixelColor(1, r, g, b);  
  led_strip.setPixelColor(2, r, g, b);  
  led_strip.setPixelColor(3, r, g, b);  
  led_strip.show(); 
}

void DrawLogo()
{
  for(int16_t i=0; i < max(display.width(),display.height())/2+12; i+=2) {
    display.clearDisplay();
    // The INVERSE color is used so circles alternate white/black
    display.fillCircle(display.width() / 2, display.height() / 2, i, SSD1306_WHITE);
    display.display(); // Update screen with each newly-drawn circle
    delay(1);
  }
  delay(100);
}

void ResetData()
{
   // Set initial default values
  data.j1PotX = j1PotXCenter; 
  data.j1PotY = j1PotYCenter;
  data.j2PotX = j2PotXCenter;
  data.j2PotY = j2PotYCenter;

  data.switch1 = 0;
  data.switch2 = 0;
  data.switch3 = 0;
  data.switch4 = 0;
  data.pot1 = 0;
  data.pot2 = 0;  
  data.button1 = 1;
  data.button2 = 1;
  data.button3 = 1;
  data.button4 = 1;
}

void UpdateLedStrip()
{
  int color_index = 0;
  float r,g,b;
  light_s = data.pot1;
  if (bLedOn)
  {
    light_v = data.pot2;      
  }
  else
  {
    light_v = 0;      
  }
  light_h = ((data.j1PotX + data.j1PotY) / 200.0f) * 360.0f;
  HSV2RGB(light_h,light_s,light_v,r,g,b);
  SetLedColors(r,g,b);
}



void CopyData()
{
  memcpy(&last_data,&data,sizeof(Data_Package));
}

void InitEEPROM()
{
  byte SaveSign = EEPROM.read(0);
  if (SaveSign != 188)
  {
    UpdateEEPROM();
  }
  else
  {
    for(int i = 0; i < sizeof(ConfigName)/sizeof(ConfigName[0]);i++)
    {
      ConfigValue[i] = EEPROM.read(i+1);
    }
  }
}

void UpdateEEPROM()
{
  EEPROM.update(0,188);
  for(int i = 0; i < sizeof(ConfigName) / sizeof(ConfigName[0]);i++)
  {
     EEPROM.update(i+1,ConfigValue[i]);
  }  
}
